﻿// Copyright (c) 2012, Alexander Endris
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#region Usings

using System.Collections.Generic;
using System.IO;

#endregion

namespace Lyricsgrabber.Core.FLAC
{
  public class FlacTag : Tag
  {
    #region Public / Attributes

    public static readonly string Tag = "fLaC";

    #endregion

    #region Public / Methods

    public override void Save()
    {
    }

    #endregion

    #region Private / Constructors

    internal FlacTag(string fileName)
    {
      using (Stream str = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
      using (BinaryReader reader = new BinaryReader(str))
      {
        string tag = new string(reader.ReadChars(4));

        if (tag != Tag)
          throw new InvalidDataException("The specified file does not contain FLAC meta data");

        if (!SeekToVorbisComment(reader))
          throw new InvalidDataException("The specified file does not contain FLAC meta data");

        int commentSize = reader.ReadByte() << 16;
        commentSize |= reader.ReadByte() << 8;
        commentSize |= reader.ReadByte();

        ReadVorbisComments(reader, commentSize);
      }
    }

    #endregion

    #region Private / Methods

    private static bool SeekToVorbisComment(BinaryReader reader)
    {
      bool lastBlock;
      FlackBlockType type;
      do
      {
        byte description = reader.ReadByte();
        lastBlock = (description & 0x80) != 0;
        type = (FlackBlockType) (description & 0x7F);

        if (type == FlackBlockType.VorbisComment)
          return true;

        int size = reader.ReadByte() << 16;
        size |= reader.ReadByte() << 8;
        size |= reader.ReadByte();

        reader.BaseStream.Seek(size, SeekOrigin.Current);
      } while (!lastBlock);

      return false;
    }

    private IList<IMetadata> ProcessComments(List<string> rawComments)
    {
      List<IMetadata> comments = new List<IMetadata>(rawComments.Count);
      foreach (var comment in rawComments)
        comments.Add(new VorbisComment(comment));
      return comments;
    }

    private void ReadVorbisComments(BinaryReader reader, int commentSize)
    {
      int start = (int) reader.BaseStream.Position;
      int vendorLength = reader.ReadInt32();
      string vendor = new string(reader.ReadChars(vendorLength));

      int numComments = reader.ReadInt32();

      List<string> rawComments = new List<string>(numComments);

      for (int i = 0; i < numComments; i++)
      {
        int commentLength = reader.ReadInt32();
        rawComments.Add(new string(reader.ReadChars(commentLength)));
      }

      metadata = ProcessComments(rawComments);
    }

    #endregion
  }
}